package com.houhanzhi.service.impl;

import com.alibaba.fastjson.TypeReference;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.houhanzhi.config.RabbitMQConfig;
import com.houhanzhi.constant.RedisKey;
import com.houhanzhi.controller.request.TrafficPageRequest;
import com.houhanzhi.controller.request.UseTrafficRequest;
import com.houhanzhi.enums.BizCodeEnum;
import com.houhanzhi.enums.EventMessageTypeEnum;
import com.houhanzhi.enums.PluginLevelEnum;
import com.houhanzhi.enums.TaskStateEnum;
import com.houhanzhi.exception.BizException;
import com.houhanzhi.feign.ProductFeignService;
import com.houhanzhi.feign.ShortLinkFeignService;
import com.houhanzhi.interceptor.LoginInterceptor;
import com.houhanzhi.manager.TrafficManager;
import com.houhanzhi.manager.TrafficTaskManager;
import com.houhanzhi.model.EventMessage;
import com.houhanzhi.model.TrafficDO;
import com.houhanzhi.model.TrafficTaskDO;
import com.houhanzhi.response.JsonData;
import com.houhanzhi.service.TrafficService;
import com.houhanzhi.util.JsonUtil;
import com.houhanzhi.util.TimeUtil;
import com.houhanzhi.vo.ProductVO;
import com.houhanzhi.vo.TrafficVO;
import com.houhanzhi.vo.UseTrafficVO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * <p>
 * 服务实现类
 * </p>
 *
 * @author houhanzhi
 * @since 2025-01-02
 */
@Service
@Slf4j
public class TrafficServiceImpl implements TrafficService {

    @Autowired
    private TrafficManager trafficManager;

    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private ProductFeignService productFeignService;

    @Autowired
    private TrafficTaskManager trafficTaskManager;

    @Autowired
    private RabbitTemplate rabbitTemplate;

    @Autowired
    private RabbitMQConfig rabbitMQConfig;

    @Autowired
    private ShortLinkFeignService shortLinkFeignService;


    /**
     * 处理流量包事件消息
     *
     * @param eventMessage
     */
    @Override
    @Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED)
    public void handleTrafficMessage(EventMessage eventMessage) {

        String messageType = eventMessage.getEventMessageType();
        Long accountNo = eventMessage.getAccountNo();

        if (messageType.equalsIgnoreCase(EventMessageTypeEnum.PRODUCT_ORDER_PAY.name())) {
            // 订单已经支付，新增流量

            // 获取订单信息
            String content = eventMessage.getContent();
            Map<String, Object> orderInfoMap = JsonUtil.json2Obj(content, Map.class);
            String outTradeNo = (String) orderInfoMap.get("outTradeNo");
            Integer buyNum = (Integer) orderInfoMap.get("buyNum");
            //还原订单商品信息
            String productStr = (String) orderInfoMap.get("product");
            ProductVO productVO = JsonUtil.json2Obj(productStr, ProductVO.class);
            log.info("商品信息:{}", productVO);

            // 流量包有效期
            LocalDateTime expiredDateTime = LocalDateTime.now().plusDays(productVO.getValidDay());
            Date date = Date.from(expiredDateTime.atZone(ZoneId.systemDefault()).toInstant());

            // 构建流量包对象
            TrafficDO trafficDO = TrafficDO.builder()
                    .accountNo(accountNo)
                    .dayLimit(productVO.getDayTimes() * buyNum) // 每日使用次数 = 流量包的日使用次数 * 购买数量
                    .dayUsed(0) // 初始化每日使用次数为0
                    .totalLimit(productVO.getTotalTimes())
                    .pluginType(productVO.getPluginType())
                    .level(productVO.getLevel())
                    .productId(productVO.getId())
                    .outTradeNo(outTradeNo)
                    .expiredDate(date).build();

            int rows = trafficManager.add(trafficDO);
            log.info("消费消息新增付费流量包:rows={},trafficDO={}", rows, trafficDO);
            // 新增流量包，应该删除这个 key，重新计算流量包的使用次数
            String totalKey = String.format(RedisKey.DAY_TOTAL_TRAFFIC, accountNo);
            redisTemplate.delete(totalKey);

        } else if (messageType.equalsIgnoreCase(EventMessageTypeEnum.TRAFFIC_FREE_INIT.name())) {
            // 发放免费流量包
            // 如果用户注册失败，但是消费成功，只是多了一条垃圾数据，实际并不影响
            // 解决方案：可以通过在此处查询 accountNo，多了一次数据库io操作

            //新用户注册初始化免费流量包  free_init 唯一索引，避免重复消费
            Long productId = Long.parseLong(eventMessage.getBizId());
            // 查询商品信息
            JsonData jsonData = productFeignService.detail(productId);
            ProductVO productVO = jsonData.getData(new TypeReference<ProductVO>() {
            });

            TrafficDO trafficDO = TrafficDO.builder()
                    .accountNo(accountNo)
                    .dayLimit(productVO.getDayTimes())
                    .dayUsed(0)
                    .totalLimit(productVO.getTotalTimes())
                    .pluginType(productVO.getPluginType())
                    .level(PluginLevelEnum.FIRST.name())
                    .productId(productVO.getId())
                    .expiredDate(new Date())
                    // 免费流量包无订单号，赋值一个默认的
                    .outTradeNo("free_init")
                    .build();
            final int rows = trafficManager.add(trafficDO);
            log.info("消费消息新增免费流量包:rows={},trafficDO={}", rows, trafficDO);
        } else if (messageType.equalsIgnoreCase(EventMessageTypeEnum.TRAFFIC_USED.name())) {
            /**
             * 流量包使用，检查是否成功使用
             * 检查task是否存在
             * 检查短链是否成功
             * 如果不成功则恢复流量包，删除缓存key
             * 删除task（也可以更新状态，定时删除也行）
             */
            Long trafficTaskId = Long.valueOf(eventMessage.getBizId());
            TrafficTaskDO trafficTaskDO = trafficTaskManager.findByIdAndAccountNo(trafficTaskId, accountNo);

            //非空 且 是锁定状态
            if (trafficTaskDO != null && trafficTaskDO.getLockState().equalsIgnoreCase(TaskStateEnum.LOCK.name())) {
                JsonData jsonData = shortLinkFeignService.check(trafficTaskDO.getBizId());
                if (jsonData.getCode() != 0) {
                    log.error("创建短链失败，流量包回滚");
                    // 查看的是当前 task 任务的创建时间
                    final String useDateStr = TimeUtil.format(trafficTaskDO.getGmtCreate(), "yyyy-MM-dd");
                    // 注意流量包跨日期问题: 1月1号晚上11点59分创建，然后失败了，1月2号凌晨后通过延迟队列又恢复了次日流量包，1月2号应该重新计算，不应该恢复1月1号流量包
                    trafficManager.releaseUsedTimes(accountNo, trafficTaskDO.getTrafficId(), trafficTaskDO.getUseTimes(), useDateStr);
                    // 恢复流量包，应该删除这个key
                    String totalTrafficTimesKey = String.format(RedisKey.DAY_TOTAL_TRAFFIC,accountNo);
                    redisTemplate.delete(totalTrafficTimesKey);
                }
                // 多种方式处理 task，不立即删除，可以更新状态，定时删除也行
                trafficTaskManager.deleteByIdAndAccountNo(trafficTaskId, accountNo);
            }
        }
    }


    /**
     * 分页查询可用流量包
     *
     * @param request
     * @return
     */
    @Override
    public JsonData pageOfAvailableTraffic(TrafficPageRequest request) {
        final int page = request.getPage();
        final int size = request.getSize();
        final Long accountNo = LoginInterceptor.threadLocal.get().getAccountNo();
        Page<TrafficDO> pageInfo = new Page<>(page, size);
        final IPage<TrafficDO> trafficDOIPage = trafficManager.pageAvailable(pageInfo, accountNo);
        final Map<String, Object> pageMap = new HashMap<>(3);
        pageMap.put("total_record", trafficDOIPage.getTotal());
        pageMap.put("total_page", trafficDOIPage.getPages());
        pageMap.put("current_data", trafficDOIPage.getRecords().stream()
                .map(this::beanProcess)
                .collect(Collectors.toList()));
        return JsonData.buildSuccess(pageMap);
    }


    /**
     * 流量包详情
     *
     * @return
     */
    @Override
    public TrafficVO detail(Long trafficId) {
        Long accountNo = LoginInterceptor.threadLocal.get().getAccountNo();
        TrafficDO trafficDO = trafficManager.findByIdAndAccountNo(trafficId, accountNo);
        return this.beanProcess(trafficDO);
    }

    /**
     * 扣减流量包
     *
     * 查询用户全部可用流量包
     *  遍历用户可用流量包
     *      判断是否更新-用日期判断
     *          没更新的流量包后加入【待更新集合】中
     *              增加【今天剩余可用总次数】
     *          已经更新的判断是否超过当天使用次数
     *              如果没超过则增加【今天剩余可用总次数】
     *              超过则忽略
     * 更新用户今日流量包相关数据
     * 扣减使用的某个流量包使用次数
     *
     * @param trafficRequest
     * @return
     */
    @Override
    @Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED)
    public JsonData reduce(UseTrafficRequest trafficRequest) {
        Long accountNo = trafficRequest.getAccountNo();

        // 处理流量包，筛选未更新流量包、当前使用流量包
        UseTrafficVO useTrafficVO = processTrafficList(accountNo);

        log.info("今天可用总次数:{}, 当前使用的流量包:{}", useTrafficVO.getDayTotalLeftTimes(), useTrafficVO.getCurrentTrafficDO());
        // 如果当前流量包为空，则没有可用流量包
        if (useTrafficVO.getCurrentTrafficDO() == null) {
            return JsonData.buildResult(BizCodeEnum.TRAFFIC_REDUCE_FAIL);
        }

        log.info("待更新流量包列表：{}", useTrafficVO.getUnUpdatedTrafficIds());
        if (useTrafficVO.getUnUpdatedTrafficIds().size() > 0) {
            // 更新今日流量包
            trafficManager.batchUpdateUsedTimes(accountNo, useTrafficVO.getUnUpdatedTrafficIds());
        }

        // 先更新，再增加此次流量包扣减
        int rows = trafficManager.addDayUsedTimes(accountNo, useTrafficVO.getCurrentTrafficDO().getId(), 1);
        if (rows != 1) {
            throw new BizException(BizCodeEnum.TRAFFIC_REDUCE_FAIL);
        }

        // 保存 task 任务，用于保证数据的最终一致性：短链创建失败，流量包扣减成功
        TrafficTaskDO trafficTaskDO = TrafficTaskDO.builder()
                .accountNo(accountNo)
                .bizId(trafficRequest.getBizId()) // 短链码id
                .useTimes(1)
                .trafficId(useTrafficVO.getCurrentTrafficDO().getId()) // 使用的流量包id
                .lockState(TaskStateEnum.LOCK.name())
                .build();
        trafficTaskManager.add(trafficTaskDO);

        // 往redis设置总量包次数，短链服务那边递减即可，如果有新增流量包，则删除该key
        // 获取当天剩余的秒数，用于流量包过期配置
        long leftSeconds = TimeUtil.getRemainSecondsOneDay(new Date());
        String totalKey = String.format(RedisKey.DAY_TOTAL_TRAFFIC, accountNo);
        // 减少1是减去此次的流量次数
        redisTemplate.opsForValue().set(totalKey, useTrafficVO.getDayTotalLeftTimes() - 1, leftSeconds, TimeUnit.SECONDS);

        // 发送延迟信息,用于异常回滚，保证数据最终一致性:
        final EventMessage usedTrafficEventMessage = EventMessage.builder()
                .accountNo(accountNo)
                .bizId(trafficTaskDO.getId() + "")
                .eventMessageType(EventMessageTypeEnum.TRAFFIC_USED.name())
                .build();
        rabbitTemplate.convertAndSend(rabbitMQConfig.getTrafficEventExchange(),
                rabbitMQConfig.getTrafficReleaseDelayRoutingKey(),
                usedTrafficEventMessage);


        return JsonData.buildSuccess();
    }

    /**
     * 处理流量包，筛选未更新流量包、当前使用流量包
     *
     * @param accountNo
     */
    private UseTrafficVO processTrafficList(Long accountNo) {

        // 全部可用流量包：收费+免费
        List<TrafficDO> list = trafficManager.selectAvailableTraffics(accountNo);
        if (CollectionUtils.isEmpty(list)) {
            throw new BizException(BizCodeEnum.TRAFFIC_EXCEPTION);
        }

        //天剩余可用总次数 = 总次数-已用
        Integer dayTotalLeftTimes = 0;

        //当前使用流量包
        TrafficDO currentTrafficDO = null;

        //没过期，且今天没更新的流量包(惰性删除)
        List<Long> unUpdatedTrafficIds = new ArrayList<>();

        //今天日期
        String todayStr = TimeUtil.format(new Date(), "yyyy-MM-dd");

        for (TrafficDO trafficDO : list) {
            //判断是否更新，用更新日期判断，不能用时间
            String trafficUpdateDate = TimeUtil.format(trafficDO.getGmtModified(), "yyyy-MM-dd");
            if (todayStr.equalsIgnoreCase(trafficUpdateDate)) {
                //已经更新   剩余可用 = 天总次数-已用次数
                int dayLeftTimes = trafficDO.getDayLimit() - trafficDO.getDayUsed();
                dayTotalLeftTimes += dayLeftTimes;

                //选取当次使用流量包，当天剩余次数必须大于 0(随机选取列表中可用的流量包:默认选取第一个)
                if (dayLeftTimes > 0 && currentTrafficDO == null) {
                    currentTrafficDO = trafficDO;
                }
            } else {
                //未更新
                dayTotalLeftTimes += trafficDO.getDayLimit();
                //记录未更新流量包  剩余可用 = 天总次数
                unUpdatedTrafficIds.add(trafficDO.getId());

                //选取当次使用流量包(随机选取列表中可用的流量包:默认选取第一个)
                if (currentTrafficDO == null) {
                    currentTrafficDO = trafficDO;
                }
            }
        }

        UseTrafficVO useTrafficVO = new UseTrafficVO(dayTotalLeftTimes, currentTrafficDO, unUpdatedTrafficIds);
        return useTrafficVO;
    }

    private TrafficVO beanProcess(TrafficDO trafficDO) {
        final TrafficVO trafficVO = new TrafficVO();
        BeanUtils.copyProperties(trafficDO, trafficVO);

        /**
         * 问题点
         *  流量包是用的时候才去检查更新，那如果用户昨天用了但今天没用
         *  查看的总的流量包时候，还是旧的流量包
         */
        //惰性更新，前端显示问题，根据更新时间进行判断是否是最新的

        //今天日期
        String todayStr = TimeUtil.format(new Date(), "yyyy-MM-dd");
        String trafficUpdateDate = TimeUtil.format(trafficDO.getGmtModified(), "yyyy-MM-dd");

        //日期不一样，则未更新，dayUsed需要为0
        if (!todayStr.equalsIgnoreCase(trafficUpdateDate)) {

            trafficVO.setDayUsed(0);
        }

        return trafficVO;
    }

    /**
     * 删除过期流量包
     */
    @Override
    public boolean deleteExpiredTraffic() {
        return trafficManager.deleteExpiredTraffic();
    }
}
